
package creature;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;

import audio.Audio;
import audio.BombSample;
import boids.Boid2D;
import boids.SteeringBehaviors;

import processing.core.*;
import toolib.util.MathUtil;
import toxi.physics2d.*;

import toxi.geom.*;
import toxi.math.*;

public class Creature extends Boid2D
{
	public static final int					STATE_NORMAL				= 0;
	public static final int					STATE_WALL					= 1;
	public static final int					STATE_BOMB					= 2;
	public static final int					STATE_SPEED					= 3;
	public static final int					STATE_EXPLODED				= 4;
	public static final int					STATE_HASFLAG				= 5;
	public static final float				PATH_STOP_DISTANCE		= 5.0f;

	private int									gameState					= STATE_NORMAL;

	private VerletPhysics2D					physics;
	private VerletParticle2D				shapeConnector;

	private ArrayList<VerletParticle2D>	tailParticles1;
	private ArrayList<VerletParticle2D>	tailParticles2;
	private ArrayList<VerletParticle2D>	tailParticles3;
	private ArrayList<VerletParticle2D>	tailParticles4;
	private ArrayList<VerletSpring2D>	tailSprings1;
	private ArrayList<VerletSpring2D>	tailSprings2;
	private ArrayList<VerletSpring2D>	tailSprings3;
	private ArrayList<VerletSpring2D>	tailSprings4;

	private RigidBody							body;
	private CircleLayout						normalMode;
	private CircleLayout						speedMode;
	private CircleLayout						bombMode;
	private RectangleLayout					wallMode;
	private CircleLayout						flagMode;

	private BombSample						bombSample;

	float											centerImageBaseScale		= 0.8f;
	float											speedImageBaseScale		= 1.0f;
	float											speedImageRotation		= 0.0f;
	float											speedImageRotationSpeed	= 0.15f;
	float											ringBombImageScale		= 1.0f;
	float											tailRingBaseScale			= 0.75f;
	Vec2D											rotation						= new Vec2D();

	private PImage								ringImage;
	private PImage								bombRingImage;
	private PImage								centerImage1;
	private PImage								centerImage2;
	private PImage								speedImage;
	private PImage								flagImage;

	private long								bombTime						= 4000;
	private long								fadeTime						= 2000;
	private long								bombStart;
	private long								fadeStart;
	private long								deadTime						= 4000;

	private float[]							rotateAngles;
	private float								rotateAngleSpeed			= 0.1f;

	public Creature(Vec2D _pos)
	{
		super(_pos);
		physics = new VerletPhysics2D();
		physics.gravity = new Vec2D(0.0f, 0.1f);
		physics.friction = 1.5f;
		physics.timeStep = 0.5f;

		normalMode = new CircleLayout(10);
		normalMode.generate(_pos, 12.0f);
		speedMode = new CircleLayout(10);
		speedMode.generate(_pos, 15.0f);
		bombMode = new CircleLayout(10);
		bombMode.generate(_pos, 15.0f);
		wallMode = new RectangleLayout(10);
		wallMode.generate(_pos, 130.0f, 8.0f);
		flagMode = new CircleLayout(10);
		flagMode.generate(_pos, 15.0f);

		body = new RigidBody(physics, true);
		body.init(normalMode);

		shapeConnector = new VerletParticle2D(_pos);
		shapeConnector.lock();
		physics.addParticle(shapeConnector);
		body.setConnector(shapeConnector);

		tailParticles1 = new ArrayList<VerletParticle2D>();
		tailParticles2 = new ArrayList<VerletParticle2D>();
		tailParticles3 = new ArrayList<VerletParticle2D>();
		tailParticles4 = new ArrayList<VerletParticle2D>();

		tailSprings1 = new ArrayList<VerletSpring2D>();
		tailSprings2 = new ArrayList<VerletSpring2D>();
		tailSprings3 = new ArrayList<VerletSpring2D>();
		tailSprings4 = new ArrayList<VerletSpring2D>();

		initTail1();
		initTail2();
		initTail3();
		initTail4();

		applyTailSeparationForces();

		rotateAngles = new float[body.getBodyParticles().size()];
		for (int i = 0; i < rotateAngles.length; i++)
		{
			rotateAngles[i] = body.getBodyParticleAngles()[i];
		}
	}

	public void initTail1()
	{
		float nodeSpacing = 5.0f;
		int numBranchNodes = 9;
		float sf = 1.0f;
		float td = 1.0f / (float) numBranchNodes;

		VerletParticle2D p = null;
		VerletParticle2D pre = null;
		for (int i = 0; i < numBranchNodes; i++)
		{
			VerletSpring2D s;
			if (pre == null)
			{
				p = new VerletParticle2D(new Vec2D(body.getCenter().x, body.getCenter().y));
				physics.addParticle(p);
				s = new VerletSpring2D(body.getCenter(), p, nodeSpacing, 0.5f * sf);
				physics.addSpring(s);
			}
			else
			{
				p = new VerletParticle2D(new Vec2D(pre.x, pre.y));
				physics.addParticle(p);
				s = new VerletSpring2D(pre, p, nodeSpacing, 0.5f * sf);
				physics.addSpring(s);
			}

			tailParticles1.add(p);
			tailSprings1.add(s);
			pre = p;

			sf -= td;
		}
	}

	public void initTail2()
	{
		float nodeSpacing = 5.0f;
		int numBranchNodes = 7;
		int startIndex = 3;
		float sf = 1.0f;
		float td = 1.0f / (float) numBranchNodes;

		VerletParticle2D p = null;
		VerletParticle2D pre = tailParticles1.get(startIndex);
		VerletSpring2D s = null;
		for (int i = 0; i < numBranchNodes; i++)
		{
			p = new VerletParticle2D(new Vec2D(pre.x, pre.y));
			s = new VerletSpring2D(pre, p, nodeSpacing, 0.1f * sf);

			physics.addParticle(p);
			physics.addSpring(s);

			tailParticles2.add(p);
			tailSprings2.add(s);

			pre = p;
			sf -= td;
		}
	}

	public void initTail3()
	{
		float nodeSpacing = 5.0f;
		int numBranchNodes = 6;
		int startIndex = 5;
		float sf = 1.0f;
		float td = 1.0f / (float) numBranchNodes;

		VerletParticle2D p = null;
		VerletParticle2D pre = tailParticles1.get(startIndex);
		VerletSpring2D s = null;
		for (int i = 0; i < numBranchNodes; i++)
		{
			p = new VerletParticle2D(new Vec2D(pre.x, pre.y));
			s = new VerletSpring2D(pre, p, nodeSpacing, 0.1f * sf);

			physics.addParticle(p);
			physics.addSpring(s);

			tailParticles3.add(p);
			tailSprings3.add(s);

			pre = p;
			sf -= td;
		}
	}

	public void initTail4()
	{
		float nodeSpacing = 5.0f;
		int numBranchNodes = 6;
		int startIndex = 4;
		float sf = 1.0f;
		float td = 1.0f / (float) numBranchNodes;

		VerletParticle2D p = null;
		VerletParticle2D pre = tailParticles1.get(startIndex);
		VerletSpring2D s = null;
		for (int i = 0; i < numBranchNodes; i++)
		{
			p = new VerletParticle2D(new Vec2D(pre.x, pre.y));
			s = new VerletSpring2D(pre, p, nodeSpacing, 0.1f * sf);

			physics.addParticle(p);
			physics.addSpring(s);

			tailParticles4.add(p);
			tailSprings4.add(s);

			pre = p;
			sf -= td;
		}
	}

	public void applyTailSeparationForces()
	{
		VerletParticle2D a = tailParticles1.get(tailParticles1.size() - 1);
		VerletParticle2D b = tailParticles2.get(tailParticles2.size() - 1);
		VerletParticle2D c = tailParticles3.get(tailParticles3.size() - 1);
		VerletParticle2D d = tailParticles4.get(tailParticles4.size() - 1);

		physics.addSpring(new OscillatingVerletSpring2D(b, a, 30.0f, 0.2f, body.getOsc(), body.getOscInfluence() * 2));
		physics.addSpring(new OscillatingVerletSpring2D(c, b, 30.0f, 0.2f, body.getOsc(), body.getOscInfluence() * 2));
		physics.addSpring(new OscillatingVerletSpring2D(d, c, 30.0f, 0.2f, body.getOsc(), body.getOscInfluence() * 2));

	}

	public void setRingImage(PImage _ringImage)
	{
		ringImage = _ringImage;
	}

	public void setBombRingImage(PImage _bombRingImage)
	{
		bombRingImage = _bombRingImage;
	}

	public void setCenterImage1(PImage _centerImage1)
	{
		centerImage1 = _centerImage1;
	}

	public void setCenterImage2(PImage _centerImage2)
	{
		centerImage2 = _centerImage2;
	}

	public void setSpeedImage(PImage _speedImage)
	{
		speedImage = _speedImage;
	}

	public void setFlagImage(PImage _flagImage)
	{
		flagImage = _flagImage;
	}

	public void update()
	{
		super.update();
		Vec2D gravity = new Vec2D(getVelocity().getNormalized());
		gravity.scaleSelf(-0.1f);
		physics.gravity.set(gravity);
		physics.update();
		body.update();
		shapeConnector.set(getPosition());

		if (shapeConnector.sub(body.getCenter()).magSquared() > 1.0f)
		{
			rotation.set(shapeConnector.sub(body.getCenter()).getNormalized());
			float aDist = (float) Math.cos(rotation.dot(getVelocity()));
			rotation.rotate(aDist / 50.0f);
		}

		DrawnPath pathToFollow = (DrawnPath) getPathToFollow();
		if (pathToFollow != null && getPosition().sub(pathToFollow.getEnd()).magnitude() < PATH_STOP_DISTANCE)
		{
			pathToFollow.setReached();
			setMode(SteeringBehaviors.NONE);
		}

		speedImageRotation += speedImageRotationSpeed;
		speedImageRotation %= (float) 2.0f * Math.PI;

		switch (gameState)
		{
			case STATE_BOMB:
				long now = new Date().getTime();
				long timePassed = now - bombStart;
				if (timePassed >= bombTime)
				{
					setGameState(STATE_EXPLODED);
				}
				break;
			case STATE_EXPLODED:
				body.wanderParticles();
				long timeDead = new Date().getTime() - fadeStart;
				if (timeDead > deadTime)
				{
					setPosition(new Vec2D((float)Math.random()*CreatureTest.GAME_WIDTH, (float)Math.random()*CreatureTest.GAME_HEIGHT));
					setGameState(STATE_NORMAL);	
				}
				break;
		}
	}

	private void explode()
	{
		body.disableSprings();
	}

	private void resetExplode()
	{
		body.resetWanderProperties();
		body.enableSprings();
		bombSample.stop();
	}

	public void setGameState(int _gameState)
	{
		switch (gameState)
		{
			case STATE_BOMB:
			case STATE_EXPLODED:
				resetExplode();
		}

		gameState = _gameState;
		switch (gameState)
		{
			case STATE_NORMAL:
				Audio.playBlub();
				body.set(normalMode);
				body.setOscInfluence(RigidBody.NORMAL_OSC_INFLUENCE);
				break;
			case STATE_WALL:
				Audio.playWiggle();
				body.set(wallMode);
				body.setOscInfluence(RigidBody.WALL_OSC_INFLUENCE);
				body.rotateWall(getVelocity().heading());
				break;
			case STATE_SPEED:
				body.set(speedMode);
				body.setOscInfluence(RigidBody.NORMAL_OSC_INFLUENCE);
				break;
			case STATE_BOMB:
				body.set(bombMode);
				body.setOscInfluence(RigidBody.BOMB_OSC_INFLUENCE);
				bombStart = new Date().getTime();
				bombSample = new BombSample();
				break;
			case STATE_EXPLODED:
				bombSample.explode();
				fadeStart = new Date().getTime();
				explode();
				break;
			case STATE_HASFLAG:
				body.set(flagMode);
				body.setOscInfluence(RigidBody.NORMAL_OSC_INFLUENCE);
				break;
		}
	}
	
	public int getGameState()
	{
		return gameState;
	}

	public void render(PApplet _p)
	{
		renderNormal(_p);
		// renderDebug(_p);
	}

	public void renderNormal(PApplet _p)
	{
		renderBody(_p);
		renderTail(_p);
		renderRing(_p);
	}

	public void renderBody(PApplet _p)
	{
		float s;
		_p.imageMode(PApplet.CENTER);

		switch (gameState)
		{
			case STATE_NORMAL:
				_p.imageMode(PApplet.CENTER);
				_p.pushMatrix();
				_p.translate(body.getCenter().x, body.getCenter().y);
				s = centerImageBaseScale + (body.getOsc().value * centerImageBaseScale * body.getOscInfluence());
				_p.scale(s);
				_p.rotate(rotation.heading() + MathUtils.PI);
				_p.noTint();
				_p.image(centerImage2, 0.0f, 0.0f);
				_p.image(centerImage1, 0.0f, 0.0f);
				_p.popMatrix();
				break;

			case STATE_WALL:
				_p.fill(0.0f, 192.0f, 255.0f, 50.0f);
				_p.noStroke();
				_p.beginShape();
				for (int i = 0; i < body.getBodyParticles().size(); i++)
				{
					VerletParticle2D p = body.getBodyParticles().get(i);
					_p.vertex(p.x, p.y);
				}
				_p.endShape();
				break;

			case STATE_SPEED:
				_p.imageMode(PApplet.CENTER);
				_p.pushMatrix();
				_p.translate(body.getCenter().x, body.getCenter().y);
				s = speedImageBaseScale + (body.getOsc().value * centerImageBaseScale * body.getOscInfluence());
				_p.scale(s);
				_p.rotate(rotation.heading() + MathUtils.PI);
				_p.image(centerImage2, 0.0f, 0.0f);
				_p.image(centerImage1, 0.0f, 0.0f);

				_p.pushMatrix();
				_p.rotate(speedImageRotation);
				_p.noTint();
				_p.image(speedImage, 0.0f, 0.0f);
				_p.popMatrix();
				_p.popMatrix();
				break;

			case STATE_BOMB:
				_p.imageMode(PApplet.CENTER);
				_p.pushMatrix();
				_p.translate(body.getCenter().x, body.getCenter().y);
				s = ringBombImageScale + (float) (new Date().getTime() - bombStart) / (float) bombTime * ringBombImageScale * 0.3f;
				_p.scale(s);
				_p.rotate(rotation.heading() + MathUtils.PI);
				_p.noTint();
				_p.image(centerImage2, 0.0f, 0.0f);
				_p.image(centerImage1, 0.0f, 0.0f);
				_p.popMatrix();
				break;

			case STATE_EXPLODED:
				_p.imageMode(PApplet.CENTER);
				_p.pushMatrix();
				_p.translate(body.getCenter().x, body.getCenter().y);
				s = ringBombImageScale + (float) (new Date().getTime() - bombStart) / (float) bombTime * ringBombImageScale * 0.3f;
				_p.scale(s);
				_p.rotate(rotation.heading() + MathUtils.PI);
				float alpha = 255.0f - 255.0f * (float) (new Date().getTime() - bombStart) / (float) bombTime * ringBombImageScale;
				_p.tint(255.0f, alpha);
				_p.image(centerImage2, 0.0f, 0.0f);
				_p.image(centerImage1, 0.0f, 0.0f);
				_p.popMatrix();
				break;

			case STATE_HASFLAG:
				_p.imageMode(PApplet.CENTER);
				_p.pushMatrix();
				_p.translate(body.getCenter().x, body.getCenter().y);
				s = centerImageBaseScale + (body.getOsc().value * centerImageBaseScale * body.getOscInfluence());
				_p.scale(s);
				_p.rotate(rotation.heading() + MathUtils.PI);
				_p.noTint();
				_p.image(flagImage, 0.0f, 0.0f);
				// _p.image(centerImage1, 0.0f, 0.0f);
				_p.popMatrix();
				break;
		}
	}

	public void renderRing(PApplet _p)
	{
		_p.imageMode(PApplet.CENTER);

		switch (gameState)
		{
			case STATE_NORMAL:
				for (Iterator i = body.getBodyParticles().iterator(); i.hasNext();)
				{
					VerletParticle2D p = (VerletParticle2D) i.next();
					_p.pushMatrix();
					_p.translate(p.x, p.y);
					_p.noTint();
					_p.image(ringImage, 0, 0);
					_p.popMatrix();
				}
				break;

			case STATE_WALL:
				VerletParticle2D prev = null;
				for (int i = 0; i < body.getBodyParticles().size(); i++)
				{
					VerletParticle2D p = (VerletParticle2D) body.getBodyParticles().get(i);

					// fill space between nodes with dummy ring images (no underlying
					// particle)
					if (prev != null)
					{
						Vec2D fillRingPositions = p.sub(prev);
						int numFillImages = Math.round(fillRingPositions.magnitude() / (float) ringImage.width);
						fillRingPositions.scaleSelf(1.0f / (float) numFillImages);
						Vec2D ringPos = fillRingPositions.add(prev);
						for (int j = 0; j < numFillImages - 1; j++)
						{
							_p.pushMatrix();
							_p.translate(ringPos.x, ringPos.y);
							_p.image(ringImage, 0, 0);
							_p.popMatrix();

							ringPos.addSelf(fillRingPositions);
						}
					}

					_p.pushMatrix();
					_p.translate(p.x, p.y);
					_p.noTint();
					_p.image(ringImage, 0, 0);
					_p.popMatrix();

					prev = p;
				}
				break;

			case STATE_SPEED:
				for (Iterator i = body.getBodyParticles().iterator(); i.hasNext();)
				{
					Vec2D p = ((VerletParticle2D) i.next()).sub(body.getCenter()).getRotated(0.2f * speedImageRotation).add(body.getCenter());

					_p.pushMatrix();
					_p.translate(p.x, p.y);
					_p.noTint();
					_p.image(ringImage, 0, 0);
					_p.popMatrix();
				}
				break;

			case STATE_BOMB:
				for (Iterator i = body.getBodyParticles().iterator(); i.hasNext();)
				{
					VerletParticle2D p = (VerletParticle2D) i.next();
					_p.pushMatrix();
					_p.translate(p.x, p.y);
					float s = ringBombImageScale + (float) (new Date().getTime() - bombStart) / (float) bombTime;
					s = (s > 1.0f) ? 1.0f : s;
					float a = p.sub(body.getCenter()).heading() + (float) Math.PI;
					_p.scale(s);
					_p.rotate(a);
					_p.noTint();
					_p.image(bombRingImage, 0, 0);
					_p.popMatrix();
				}
				break;

			case STATE_EXPLODED:
				for (int i = 0; i < body.getBodyParticles().size(); i++)
				{
					VerletParticle2D p = body.getBodyParticles().get(i);
					_p.pushMatrix();
					_p.translate(p.x, p.y);
					float s = ringBombImageScale + (float) (new Date().getTime() - fadeStart) / (float) fadeTime;
					s = (s > 1.0f) ? 1.0f : s;
					float a = body.getBodyParticleAngles()[i] + rotateAngles[i];
					float timeRel = (float) (new Date().getTime() - fadeStart) / (float) fadeTime;
					timeRel = (timeRel > 1.0f) ? 1.0f : timeRel;
					float alpha = 255.0f - 255.0f * MathUtil.quartOut(0.0f, 1.0f, timeRel);
					_p.scale(s);
					_p.rotate(a);
					_p.tint(255.0f, alpha);
					_p.image(bombRingImage, 0, 0);
					_p.popMatrix();

					rotateAngles[i] += rotateAngleSpeed;
					rotateAngles[i] %= (float) Math.PI * 2.0f;
				}
				break;

			case STATE_HASFLAG:
				for (Iterator i = body.getBodyParticles().iterator(); i.hasNext();)
				{
					VerletParticle2D p = (VerletParticle2D) i.next();
					_p.pushMatrix();
					_p.translate(p.x, p.y);
					_p.noTint();
					_p.image(ringImage, 0, 0);
					_p.popMatrix();
				}
				break;
		}
	}

	public void renderTail(PApplet _p)
	{
		switch (gameState)
		{
			case STATE_EXPLODED:
				float timeRel = (float) (new Date().getTime() - fadeStart) / (float) fadeTime;
				timeRel = (timeRel > 1.0f) ? 1.0f : timeRel;
				float sAlpha = 50.0f - 50.0f * MathUtil.quartOut(0.0f, 1.0f, timeRel);
				_p.noFill();
				_p.stroke(255.0f, sAlpha);
				_p.strokeWeight(1.0f);
				float tAlpha = 255.0f - 255.0f * MathUtil.quartOut(0.0f, 1.0f, timeRel);
				_p.tint(255.0f, tAlpha);
				tailRingBaseScale = 0.9f;
				break;
			case STATE_BOMB:
				tailRingBaseScale = 0.9f;
				break;
			default:
				_p.noFill();
				_p.stroke(255.0f, 50.0f);
				_p.strokeWeight(1.0f);
				_p.noTint();
				tailRingBaseScale = 0.75f;
		}

		for (Iterator i = tailSprings1.iterator(); i.hasNext();)
		{
			VerletSpring2D s = (VerletSpring2D) i.next();
			_p.line(s.a.x, s.a.y, s.b.x, s.b.y);
		}

		for (Iterator i = tailSprings2.iterator(); i.hasNext();)
		{
			VerletSpring2D s = (VerletSpring2D) i.next();
			_p.line(s.a.x, s.a.y, s.b.x, s.b.y);
		}

		for (Iterator i = tailSprings3.iterator(); i.hasNext();)
		{
			VerletSpring2D s = (VerletSpring2D) i.next();
			_p.line(s.a.x, s.a.y, s.b.x, s.b.y);
		}

		for (Iterator i = tailSprings4.iterator(); i.hasNext();)
		{
			VerletSpring2D s = (VerletSpring2D) i.next();
			_p.line(s.a.x, s.a.y, s.b.x, s.b.y);
		}

		int numTotal = tailParticles1.size();
		int index = 0;
		for (Iterator i = tailParticles1.iterator(); i.hasNext();)
		{
			VerletParticle2D p = (VerletParticle2D) i.next();
			_p.pushMatrix();
			_p.translate(p.x, p.y);
			_p.scale(tailRingBaseScale * (float) (numTotal - index) / (float) numTotal);
			_p.image(ringImage, 0, 0);
			_p.popMatrix();
			index++;
		}

		numTotal = tailParticles2.size();
		index = 0;
		for (Iterator i = tailParticles2.iterator(); i.hasNext();)
		{
			VerletParticle2D p = (VerletParticle2D) i.next();
			_p.pushMatrix();
			_p.translate(p.x, p.y);
			_p.scale(tailRingBaseScale * (float) (numTotal - index) / (float) numTotal);
			_p.image(ringImage, 0, 0);
			_p.popMatrix();
			index++;
		}

		numTotal = tailParticles3.size();
		index = 0;
		for (Iterator i = tailParticles3.iterator(); i.hasNext();)
		{
			VerletParticle2D p = (VerletParticle2D) i.next();
			_p.pushMatrix();
			_p.translate(p.x, p.y);
			_p.scale(tailRingBaseScale * (float) (numTotal - index) / (float) numTotal);
			_p.image(ringImage, 0, 0);
			_p.popMatrix();
			index++;
		}

		numTotal = tailParticles4.size();
		index = 0;
		for (Iterator i = tailParticles4.iterator(); i.hasNext();)
		{
			VerletParticle2D p = (VerletParticle2D) i.next();
			_p.pushMatrix();
			_p.translate(p.x, p.y);
			_p.scale(tailRingBaseScale * (float) (numTotal - index) / (float) numTotal);
			_p.image(ringImage, 0, 0);
			_p.popMatrix();
			index++;
		}
	}

	public void renderDebug(PApplet _p)
	{
		// super.render(_p.g);

		_p.stroke(255, 50);
		_p.noFill();
		_p.strokeWeight(1.0f);

		for (Iterator i = physics.springs.iterator(); i.hasNext();)
		{
			VerletSpring2D s = (VerletSpring2D) i.next();
			_p.line(s.a.x, s.a.y, s.b.x, s.b.y);
		}

		_p.fill(255);
		_p.noStroke();

		for (Iterator i = physics.particles.iterator(); i.hasNext();)
		{
			VerletParticle2D p = (VerletParticle2D) i.next();
			_p.ellipse(p.x, p.y, 5, 5);
		}
	}
}
